/*
 * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package javax.swing;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.*;
import java.lang.ref.WeakReference;
import java.lang.ref.ReferenceQueue;

/**
 * A package-private PropertyChangeListener which listens for
 * property changes on an Action and updates the properties
 * of an ActionEvent source.
 * <p>
 * Subclasses must override the actionPropertyChanged method,
 * which is invoked from the propertyChange method as long as
 * the target is still valid.
 * </p>
 * <p>
 * WARNING WARNING WARNING WARNING WARNING WARNING:<br>
 * Do NOT create an annonymous inner class that extends this!  If you do
 * a strong reference will be held to the containing class, which in most
 * cases defeats the purpose of this class.
 *
 * @param T the type of JComponent the underlying Action is attached to
 * @author Georges Saab
 * @see AbstractButton
 */
abstract class ActionPropertyChangeListener<T extends JComponent>
    implements PropertyChangeListener, Serializable {

  private static ReferenceQueue<JComponent> queue;

  // WeakReference's aren't serializable.
  private transient OwnedWeakReference<T> target;
  // The Component's that reference an Action do so through a strong
  // reference, so that there is no need to check for serialized.
  private Action action;

  private static ReferenceQueue<JComponent> getQueue() {
    synchronized (ActionPropertyChangeListener.class) {
      if (queue == null) {
        queue = new ReferenceQueue<JComponent>();
      }
    }
    return queue;
  }

  public ActionPropertyChangeListener(T c, Action a) {
    super();
    setTarget(c);
    this.action = a;
  }

  /**
   * PropertyChangeListener method.  If the target has been gc'ed this
   * will remove the <code>PropertyChangeListener</code> from the Action,
   * otherwise this will invoke actionPropertyChanged.
   */
  public final void propertyChange(PropertyChangeEvent e) {
    T target = getTarget();
    if (target == null) {
      getAction().removePropertyChangeListener(this);
    } else {
      actionPropertyChanged(target, getAction(), e);
    }
  }

  /**
   * Invoked when a property changes on the Action and the target
   * still exists.
   */
  protected abstract void actionPropertyChanged(T target, Action action,
      PropertyChangeEvent e);

  private void setTarget(T c) {
    ReferenceQueue<JComponent> queue = getQueue();
    // Check to see whether any old buttons have
    // been enqueued for GC.  If so, look up their
    // PCL instance and remove it from its Action.
    OwnedWeakReference<?> r;
    while ((r = (OwnedWeakReference) queue.poll()) != null) {
      ActionPropertyChangeListener<?> oldPCL = r.getOwner();
      Action oldAction = oldPCL.getAction();
      if (oldAction != null) {
        oldAction.removePropertyChangeListener(oldPCL);
      }
    }
    this.target = new OwnedWeakReference<T>(c, queue, this);
  }

  public T getTarget() {
    if (target == null) {
      // Will only happen if serialized and real target was null
      return null;
    }
    return this.target.get();
  }

  public Action getAction() {
    return action;
  }

  private void writeObject(ObjectOutputStream s) throws IOException {
    s.defaultWriteObject();
    s.writeObject(getTarget());
  }

  @SuppressWarnings("unchecked")
  private void readObject(ObjectInputStream s)
      throws IOException, ClassNotFoundException {
    s.defaultReadObject();
    T target = (T) s.readObject();
    if (target != null) {
      setTarget(target);
    }
  }


  private static class OwnedWeakReference<U extends JComponent> extends
      WeakReference<U> {

    private ActionPropertyChangeListener<?> owner;

    OwnedWeakReference(U target, ReferenceQueue<? super U> queue,
        ActionPropertyChangeListener<?> owner) {
      super(target, queue);
      this.owner = owner;
    }

    public ActionPropertyChangeListener<?> getOwner() {
      return owner;
    }
  }
}
